package Parser;
/**
 * <p>Title: </p>
 *
 * <p>Description: </p>
 *
 * <p>Copyright: Copyright (c) 2006</p>
 *
 * <p>Company: </p>
 *
 * @author not attributable
 * @version 1.0
 */


import java.io.*;
import java.util.*;

public class ParseTree {
    private Token[] tokens;
    private String name;

    public ParseTree(BufferedReader read) throws Exception {
        analyze(read);
        init(tokens, false, false);
    }

    public ParseTree(StringBuffer data) throws Exception {
        analyze(data);
        init(tokens, false, false);
    }

    public ParseTree(Token[] data)
    {
        init(data, false, false);
    }

    public ParseTree(BufferedReader read, boolean removeScripts) throws Exception {
        analyze(read);
        init(tokens, removeScripts, false);
    }

    public ParseTree(StringBuffer data, boolean removeScripts) throws Exception {
        analyze(data);
        init(tokens, removeScripts, false);
    }

    public ParseTree(Token[] data, boolean removeScripts)
    {
        init(data, removeScripts, false);
    }

    public ParseTree(BufferedReader read, boolean removeScripts, boolean trimHTML) throws Exception {
        analyze(read);
        init(tokens, removeScripts, trimHTML);
    }

    public ParseTree(StringBuffer data, boolean removeScripts, boolean trimHTML) throws Exception {
        analyze(data);
        init(tokens, removeScripts, trimHTML);
    }

    public ParseTree(Token[] data, boolean removeScripts, boolean trimHTML)
    {
        init(data, removeScripts, trimHTML);
    }


    private void init(Token[] data, boolean removeScripts, boolean dropToHTML)
    {
        if (removeScripts)
          tokens = removeScripts(data);

        tokens = validateCorrect(tokens);
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public String getName()
    {
        return name;
    }
    
    // Counts all occurances of Type type starting from and including token
    // Stopping at and not including stop
    public int countOccurances(int token, int stop, int type) {
      int count = 0;
      
      for (int c = token; c < stop; c++) {
          if (tokens[c].getType() == type) {
            count++;
          }
      }
      
      return count;
    }
    
    public int countOccurances(int token, int type) {
      int count = 0;
      
      for (int c = token; c < tokens.length; c++) {
          if (tokens[c].getType() == type) {
            count++;
          }
      }
      
      return count;
    }
    
    public int countOccurances(int type) {
      int count = 0;
      
      for (int c = 0; c < tokens.length; c++) {
          if (tokens[c].getType() == type) {
            count++;
          }
      }
      
      return count;
    }

    public boolean contains(int token, int type)
    {
      int close = findClose(token);
      Token tag;

      for (int c = token; c < close; c++)
      {
        tag = tokens[c];
        if ( tag.getType() == type) {
          return true;
        }
      }

      return false;
    }

    public boolean containsOnly(int token, int type)
    {
        int close = findClose(token);
        Token tag;

        for (int c = token + 1; c < close; c++) {
            tag = tokens[c];
            if (tag.getType() != type) {
                return false;
            }
        }

        return true;
    }

    public int getBodyTagIndex()
    {
        int index = 0;

        while (index < tokens.length && tokens[index].getType() != Token.BODY)
          index++;

        if (index < tokens.length)
            return index;
        else
            return -1;
    }

    public Token[] getTokens()
    {
        return tokens;
    }

    // Takes the opening
    public StringBuffer getAllTextInsideToken(int token)
    {
        int close = findClose(token);
        StringBuffer text = new StringBuffer();
        Token tag;

        for (int c = token; c < close; c++)
        {
            tag = tokens[c];
            if (tag.getType() == Token.TEXT )
            {
                text.append(tag.toString());
                text.append(' ');
            }
        }

        return text;
    }

    public StringBuffer getAllText(int start, int stop)
    {
        StringBuffer text = new StringBuffer();
        Token tag;

        for (int c = start; c < stop; c++)
        {
            tag = tokens[c];
            if (tag.getType() == Token.TEXT )
            {
                text.append(tag.toString());
                text.append(' ');
            }
        }

        return text;
    }

    public int getWordCountInsideToken(int token)
    {
        int close = findClose(token);
        int count = 0;
        Token tag;

        for (int c = token; c < close; c++)
        {
            tag = tokens[c];
            if (tag.getType() == Token.TEXT )
            {
                count++;
            }
        }

        return count;
    }

    public int getTextCount(int start, int stop)
    {
        Token tag;
        int count = 0;

        for (int c = start; c < stop; c++)
        {
            tag = tokens[c];
            if (tag.getType() == Token.TEXT )
            {
                count++;
            }

        }

        return count;
    }

    public int findClose(int token)
    {
        int counter;
        Token start = tokens[token];
        Token tag;

        counter = token + 1;
        while (counter < tokens.length)
        {
            tag = tokens[counter];
            if (tag.closes(start))
              return counter;
            else if (tag.getType() == start.getType())
            {
              try {
                counter = findClose(counter) + 1;
              }
              catch (HTMLException e)
              {
                throw new HTMLException("Malformed Html - Tag is not closed:  " + start.toString());
              }
            }
            else
              counter++;
        }

        throw new HTMLException("Malformed Html - Tag is not closed:  " + start.toString());
    }

    public boolean isClosed(ArrayList<Token> data, int token)
    {
        int counter;
        Token start = data.get(token);
        Token tag;

        counter = token + 1;
        while (counter < data.size())
        {
            tag = data.get(counter);
            if (tag.closes(start))
              return true;
            else if (tag.getType() == start.getType())
            {
                return isClosed(data, counter);
            }
            else
              counter++;
        }

        return false;
    }

    public Token[] removeScripts(Token[] data) {
        ArrayList<Token> temp;
        Token[] out;

        temp = new ArrayList<Token>(data.length);
        for (int c = 0; c < data.length; c++)
            temp.add(data[c]);

        removeScripts(temp);

        out = new Token[temp.size()];

        for (int c = 0; c < temp.size(); c++)
            out[c] = temp.get(c);

        return out;
    }

    public void removeScripts(ArrayList<Token> data)
    {
        int pos;

        for (int c = 0; c < data.size(); c++)
        {
            if (data.get(c).getType() == Token.SCRIPT)
            {
                pos = findClose(data, c);
                for (int c2 = 0; c2 < (pos - c - 1); c2++)
                {
                    data.remove(c + 1);
                }
            }
        }
    }

    public Token[] trimToHTML(Token[] data) {
        ArrayList<Token> temp;
        Token[] out;

        temp = new ArrayList<Token>(data.length);
        for (int c = 0; c < data.length; c++)
            temp.add(data[c]);

        trimToHTML(temp);

        out = new Token[temp.size()];

        for (int c = 0; c < temp.size(); c++)
            out[c] = temp.get(c);

        return out;
    }

    public void trimToHTML(ArrayList<Token> data)
    {
        int pos;

        pos = findNextTag(data, 0, Token.HTML);
        if (pos < 0)
          pos = findNextTag(data, 0, Token.BODY);

        if (pos < 0)
          return;

        for (int c = 0; c < pos; c++)
        {
            data.remove(0);
        }
    }


    public Token[] validateCorrect(Token[] data)
    {
        ArrayList<Token> temp;
        Token[] out;

        temp = new ArrayList<Token>(data.length);
        for (int c = 0; c < data.length; c++)
          temp.add(data[c]);

        validateCorrect(temp);

        out = new Token[temp.size()];

        for (int c = 0; c < temp.size(); c++)
          out[c] = temp.get(c);

        return out;
    }

    public int findNextClose(int start)
    {
        int c;

        for (c = start; c < tokens.length; c++)
        {
            if (tokens[c].isClose())
              return c;
        }

        return -1;
    }
    
    public int findPreviousClose(int start)
    {
        int c;

        for (c = start; c >= 0; c--)
        {
            if (tokens[c].isClose())
              return c;
        }

        return -1;
    }

    public int findNextTag(int start, int type)
    {
        int c;

        for (c = start; c < tokens.length; c++)
        {
            if (tokens[c].getType() == type)
              return c;
        }

        return -1;
    }

    public int findNextTag(ArrayList<Token> data, int start, int type)
    {
        int c;

        for (c = start; c < data.size(); c++)
        {
            if (data.get(c).getType() == type)
              return c;
        }

        return -1;
    }


    public int findPreviousTag(int start, int type)
    {
        int c;

        for (c = start; c >= 0; c--)
        {
            if (tokens[c].getType() == type)
              return c;
        }

        return -1;
    }

    // Returns -1 if there are insufficent tokens
    public int findIndexOfNextText(int startToken, int textTokens)
    {
        int c;

        if (textTokens == 0)
          return startToken;

        for (c = startToken; c < tokens.length; c++)
        {
            if (tokens[c].getType() == Token.TEXT)
            {
                textTokens--;
                if (textTokens <= 0)
                  return c;
            }
        }

        return -1;

    }

    // Returns -1 if there are insufficent tokens
    public int findIndexOfPreviousText(int startToken, int textTokens)
    {
        int c;

        if (textTokens == 0)
          return startToken;

        for (c = startToken; c >= 0; c--)
        {
            if (tokens[c].getType() == Token.TEXT)
            {
                textTokens--;
                if (textTokens <= 0)
                  return c;
            }
        }

        return -1;
    }

    // This takes in a document and inserts corrects and error checking
    public void validateCorrect(ArrayList<Token> data)
    {
        correctTables(data);
        correct_unclosed_tag_general(data);
        //correct_inversions(data);
    }

    // Should be called before correct_unclosed_tag_general
    private void correctTables(ArrayList<Token> data)
    {
        int counter = 0;

        while (counter < data.size())
        {
            if (data.get(counter).getType() == Token.TABLE)
            {
                counter = correct_handleTable(data, counter + 1);
            }

            counter++;
        }
    }

    private int findClose(ArrayList<Token> data, int token)
    {
        int counter;
        Token start = data.get(token);
        Token tag;

        counter = token + 1;
        while (counter < data.size())
        {
            tag = data.get(counter);
            if (tag.closes(start))
              return counter;
            else if (tag.getType() == start.getType())
            {
              try {
                counter = findClose(data, counter) + 1;
              }
              catch (HTMLException e)
              {
                throw new HTMLException("Malformed Html - Tag is not closed:  " + start.toString());
              }
            }
            else
              counter++;
        }

        throw new HTMLException("Malformed Html - Tag is not closed:  " + start.toString());
    }

    private int findOpen(ArrayList<Token> data, int token) {
        int counter;
        Token start = data.get(token);
        Token tag;

        counter = token - 1;
        while (counter >= 0) {
            tag = data.get(counter);
            if (start.closes(tag))
                return counter;
            else if (tag.getType() == start.getType()) {
                try {
                    counter = findOpen(data, counter) - 1;
                } catch (HTMLException e) {
                    throw new HTMLException(
                            "Malformed Html - Tag is not opened:  " +
                            start.toString());
                }
            } else
                counter--;
        }

        throw new HTMLException("Malformed Html - Tag is not opened:  " +
                                start.toString());
    }

    // Should only be called after correct_unclosed_tag_general
    private void correct_inversions(ArrayList<Token> data)
    {
        MalformedStack stack = new MalformedStack();
        MalformedStackElement top;
        Token temp;
        int close;

        for (int c = 0; c < data.size(); c++)
        {
            if (data.get(c).hasClose())
            {
                if (isClosed(data, c)) {
                    stack.push(data.get(c), c);
                }
            }
            else if (data.get(c).isRequiredClose())
            {
                while (!stack.isEmpty() && !data.get(c).closes(stack.peek())) {
                    // Trouble, we have found an inversion

                    top = stack.popFull(data.get(c)); // Pop it

                    // fix it
                    close = findClose(data, top.getIndex());
                    temp = data.remove(close);
                    data.add(c, temp);
                    c++;
                }

                if (!stack.isEmpty())
                {
                    stack.pop();
                }
                else
                {
                    // Rats!  A close that is not opened
                    data.remove(c);
                    c--;
                }
            }
        }

    }

    private void correct_unclosed_tag_general(ArrayList<Token> data)
    {
        MalformedStack stack = new MalformedStack();
        Token top;

        for (int c = 0; c < data.size(); c++)
        {
            if (data.get(c).hasClose())
            {
                if (!isClosed(data, c))
                {
                    stack.push(data.get(c), true);
                }
                else
                {
                    stack.push(data.get(c), false);
                }
            }
            else if (data.get(c).isRequiredClose())
            {
                if (stack.isEmpty()){
                  // Rats!  A close that is not opened
                  data.remove(c);
                  c--;
                }
                else {
                    while (!stack.isEmpty() && stack.isTopMarked())
                    {
                        // A correction is needed
                        top = stack.pop();
                        data.add(c, top.generateClose());
                        c++;
                    }

                    if (stack.isEmpty()) {
                        // Rats!  A close that is not opened
                        data.remove(c);
                        c--;
                    }
                    else
                    {
                        top = stack.pop(data.get(c));
                        if (top == null) {
                            // Rats!  A close that is not opened
                            data.remove(c);
                            c--;
                        }
                    }
                }
            }
        }

        while (!stack.isEmpty())
        {
            // A correction is needed
            top = stack.pop();
            data.add(data.size() - 1, top.generateClose());
        }

    }

    // Returns the last token examined - as in, the close tag for the table
    private int correct_handleTable(ArrayList<Token> data, int start)
    {
        int count = start;
        boolean inTD = false;
        boolean inTR = false;

        while (count < data.size() && data.get(count).getType() != Token.CLOSE_TABLE)
        {
            if (data.get(count).getType() == Token.TABLE) {
                count = correct_handleTable(data, count + 1);
            }
            else if (data.get(count).getType() == Token.TD)
            {
                if (!inTR){
                    data.add(count, new Token("<tr>"));
                    count++;
                    inTR = true;
                }

                if (inTD)
                {
                    data.add(count, new Token("</td>"));
                    count++;
                }
                else
                  inTD = true;
            }
            else if ( data.get(count).getType() == Token.TR )
            {
                if (inTD)
                {
                    data.add(count, new Token("</td>"));
                    count++;
                    inTD = false;
                }

                if (inTR)
                {
                    data.add(count, new Token("</tr>"));
                    count++;
                }
                else
                  inTR = true;
            }
            else if (data.get(count).getType() == Token.CLOSE_TD)
              inTD = false;
            else if (data.get(count).getType() == Token.CLOSE_TR)
              inTR = false;

          count++;
        }

        if (count >= data.size())
        {
            if (inTD)
            {
                data.add(count - 1, new Token("</td>"));
                count++;
            }

            if (inTR)
            {
                data.add(count - 1, new Token("</tr>"));
                count++;
            }


            // Hit the end with no close table
            data.add(count - 1, new Token("</table>"));
        }
        else
        {
            if (inTD) {
                data.add(count, new Token("</td>"));
                count++;
            }

            if (inTR) {
                data.add(count, new Token("</tr>"));
                count++;
            }
        }

        return count;
    }

    private void analyze(StringBuffer buffer) {
        LexBuffer lex = new LexBuffer(buffer);
        int size = 0;

        tokens = new Token[1];
        //lex.gotoBody();
        lex.nextToken();
        while (lex.hasToken()) {
            if (size >= tokens.length)
                setCapcity(size * 2 + 1);

            tokens[size] = new Token(lex.getToken());
            size++;
            lex.nextToken();
        }

        setCapcity(size);

    }

    private void analyze(BufferedReader read) throws Exception {
        LexStream lex = new LexStream(read);
        int size = 0;

        tokens = new Token[1];
        //lex.gotoBody();
        lex.nextToken();
        while (lex.hasToken()) {
            if (size >= tokens.length)
                setCapcity(size * 2 + 1);

            if (lex.getToken().length() > 8 && (lex.getToken().substring(0, 8).toLowerCase().equals("<script ") || 
                lex.getToken().substring(0, 8).toLowerCase().equals("<script>")) ) {
                
                // Get the script tag
                tokens[size] = new Token(lex.getToken().substring(0, lex.getToken().indexOf('>')+1));
                size++;
                if (size >= tokens.length)
                    setCapcity(size * 2 + 1);
                
                // Get the text
                // It could be empty, if so, no text
                if (lex.getToken().toLowerCase().lastIndexOf("</script") - (lex.getToken().indexOf('>') + 1) > 0) {
                  tokens[size] = new Token(lex.getToken().substring(lex.getToken().indexOf('>') + 1, lex.getToken().toLowerCase().lastIndexOf("</script")));
                  size++;
                }
                
                // Get the end tag
                if (size >= tokens.length)
                    setCapcity(size * 2 + 1);
                tokens[size] = new Token(lex.getToken().substring(lex.getToken().toLowerCase().lastIndexOf("</script")));
            }
            else
                tokens[size] = new Token(lex.getToken());       
            
            size++;
            lex.nextToken();
        }

        setCapcity(size);
    }

    private void setCapcity(int s) {
        Token[] temp = new Token[s];

        for (int c = 0; c < Math.min(s, tokens.length); c++) {
            temp[c] = tokens[c];
        }

        tokens = temp;
    }
}

